package com.ruoyi.serialPort.api;

import com.ruoyi.serialPort.api.callBack.SerialPortCallBack;
import gnu.io.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author wwy
 * @date 2021-08-06 14:33
 */
public class SerialPortDataHandle extends Thread implements SerialPortEventListener {

    private static final String[] DIGITS_UPPER =
            {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
    /**
     * 通讯端口管理，控制对通信端口的访问的中心类
     */
    static CommPortIdentifier portManager;
    /**
     * 有效连接上的端口的枚举
     */
    static Enumeration<?> portList;
    /**
     * 串口输入流引用
     */
    static InputStream inputStream;
    /**
     * 串口输出流引用
     */
    static OutputStream outputStream;
    /**
     * 串口对象引用
     */
    static SerialPort serialPort;
    /**
     * 堵塞队列：用来存放串口发送到服务端的数据
     */
    private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<>();
    /**
     * 线程控制标识
     */
    private boolean flag = true;
    /**
     * 回调函数
     */
    public static SerialPortCallBack serialPortCallBack;

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            /*
             *  SerialPortEvent.BI:/*Break interrupt,通讯中断
             *  SerialPortEvent.OE:/*Overrun error，溢位错误
             *  SerialPortEvent.FE:/*Framing error，传帧错误
             *  SerialPortEvent.PE:/*Parity error，校验错误
             *  SerialPortEvent.CD:/*Carrier detect，载波检测
             *  SerialPortEvent.CTS:/*Clear to send，清除发送
             *  SerialPortEvent.DSR:/*Data set ready，数据设备就绪
             *  SerialPortEvent.RI:/*Ring indicator，响铃指示
             *  SerialPortEvent.OUTPUT_BUFFER_EMPTY:/*Output buffer is empty，输出缓冲区清空
             */
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY: break;
            // 当有可用数据时读取数据
            case SerialPortEvent.DATA_AVAILABLE:
                // 数据接收缓冲容器
                byte[] readBuffer = new byte[200];
                try {
                    // 存储待接收读取字节数大小
                    int numBytes = 0;
                    while (inputStream.available() > 0) {
                        numBytes = inputStream.read(readBuffer);
                        if (numBytes > 0) {
                            msgQueue.add(toHex1(readBuffer));
                            // 数据接收缓冲容器清空初始化
                            readBuffer = new byte[200];
                        }
                    }
                } catch (IOException e) {
                    System.out.println("io异常");
                }
                break;
        }
    }

    public int init(String portName) {
        // 通过串口通信管理类获得当前连接上的端口列表
        //（获取一个枚举对象，该CommPortIdentifier对象包含系统中每个端口的对象集[串口、并口]）
        portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            // 获取相应串口对象
            portManager = (CommPortIdentifier) portList.nextElement();
            /*
             *  判断端口类型是否为串口
             *  PORT_SERIAL = 1; 【串口】
             *  PORT_PARALLEL = 2; 【并口】
             *  PORT_I2C = 3; 【I2C】
             *  PORT_RS485 = 4; 【RS485】
             *  PORT_RAW = 5; 【RAW】
             */

            if (portManager.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                System.out.println("串口设备名称：" + portManager.getName());
                // 判断模拟COM4串口存在，就打开该串口
                if (portManager.getName().equals(portName)) {
                    System.out.println("测试串口设备名称：" + portManager.getName());
                    try {
                        if (Objects.isNull(serialPort)) {
                            // 打开串口，设置名字为COM_4(自定义),延迟阻塞时等待3000毫秒（赋值给预设的串口引用）
                            serialPort = (SerialPort)portManager.open("COM3", 3000);
                            System.out.println("串口设备COM4已打开");
                        }
                    } catch (PortInUseException e) {
                        System.out.println("串口使用异常");
                        return 0;
                    }
                    // 在串口引用不为空时进行下述操作
                    if (Objects.nonNull(serialPort)) {
                        // 1. 设置串口的输入输出流引用
                        try {
                            inputStream = serialPort.getInputStream();
                            outputStream = serialPort.getOutputStream();
                        } catch (IOException e) {
                            System.out.println("串口输入输出IO异常");
                            return 0;
                        }
                        // 2. 设置串口监听器
                        try {
                            serialPort.addEventListener(this);
                        } catch (TooManyListenersException e) {
                            System.out.println("串口监听器添加异常");
                            return 0;
                        }
                        // 设置监听器在有数据时通知生效
                        serialPort.notifyOnDataAvailable(true);
                        // 3. 设置串口相关读写参数
                        try {
                            // 比特率、数据位、停止位、校验位
                            serialPort.setSerialPortParams(9600,
                                    SerialPort.DATABITS_8,
                                    SerialPort.STOPBITS_1,
                                    SerialPort.PARITY_NONE);
                        } catch (UnsupportedCommOperationException e) {
                            return 0;
                        }
                        return 1;
                    }
                    return 0;
                }
            }
        }
        return 0;
    }

    @Override
    public void run() {
        try {
            System.out.println("串口线程已运行");
            while (flag) {
                // 如果堵塞队列中存在数据就将其输出
                if (msgQueue.size() > 0) {
                    // take() 取走BlockingQueue里排在首位的对象
                    // 若BlockingQueue为空，阻断进入等待状态直到Blocking有新的对象被加入为止
                    if(serialPortCallBack != null){
                        System.out.println(msgQueue.take());
                        serialPortCallBack.invoke(msgQueue.take());
                    }

                }
            }
        } catch (InterruptedException e) {
            System.out.println("线程执行异常");
        }
    }

    public void stopGetDataBySerialPort() {
        this.flag = false;
    }

    public void sendData(String data) {
        try {
            byte[] bytes = hexToByteArray(data);
            for (byte aByte : bytes) {
                System.out.println(aByte);
            }
            OutputStream outputStream = serialPort.getOutputStream();
            outputStream.write(bytes);

            String s = toHex1(bytes);
            System.out.println(s);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static byte[] hexToByteArray(String hexString) {
         byte[] result = new byte[hexString.length() / 2];
        for (int len = hexString.length(), index = 0; index <= len - 1; index += 2) {
            String subString = hexString.substring(index, index + 2);
            int intValue = Integer.parseInt(subString, 16);
            result[index / 2] = (byte)intValue;
         }
        return result;
    }

    public static String toHex1(byte[] value) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : value) {
            int high = (b & 0xF0) >>> 4;
            int low = b & 0x0F;
            stringBuilder.append(DIGITS_UPPER[high] + DIGITS_UPPER[low]);
        }

        return stringBuilder.toString();
    }

    /**
     * 获取串口名称
     * @return
     */
    public List<String> getPortList(){
        List<String> portList = new ArrayList<>();
        Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
        while (portIdentifiers.hasMoreElements()){
            CommPortIdentifier commPortIdentifier = (CommPortIdentifier) portIdentifiers.nextElement();
            String name = commPortIdentifier.getName();
            portList.add(name);
        }
        return portList;
    }

    /**
     * 关闭串口
     */
    public void close(){
        serialPort.close();
    }


}
